#ifndef DW_INPUTS_VARIABLE_H
#	define DW_INPUTS_VARIABLE_H

#include <OIS/OISInputManager.h>
#include <OIS/OISException.h>
#include <OIS/OISKeyboard.h>
#include <OIS/OISMouse.h>
#include <OIS/OISEvents.h>

namespace dw {
	namespace inputs {

class InputVariableBase
{
public : //! @name Construction & Destruction
	//! @{
	InputVariableBase(OIS::Type deviceType) : m_deviceType(deviceType) { ; }
	//! @}

public : //! @name State
	//! @{
	OIS::Type getDeviceType() const { return m_deviceType; }
	//! @}

protected : //! @name Commands
	//! @{
	friend class InputCommand;

	virtual void update(const OIS::Object* device) = 0;
	//! @}

private : //! @name Data
	//! @{
	OIS::Type m_deviceType;
	//! @}
};

class InputCondition
:	public InputVariableBase
{
public : //! @name Construction & Destruction
	//! @{
	InputCondition(OIS::Type deviceType) : InputVariableBase(deviceType) { ; }
	//! @}

public : //! @name Commands
	//! @{
	virtual InputCondition* clone() const = 0;
	//! @}

public : //! @name State
	//! @{
	virtual bool isActive() const = 0;
	//! @}
};

class ButtonStateCondition
:	public InputCondition
{
public : //! @name Types definitions
	//! @{
	enum State
	{
		Invalid = 0,
		Down = 0x1 << 1,
		Pressed = Down | 0x1,
		Up = 0x1 << 2,
		Released = Up | 0x1,
	};
	//! @}

public : //! @name Construction & Destruction
	//! @{
	ButtonStateCondition(OIS::Type deviceType, State desiredState) : InputCondition(deviceType), m_desiredState(desiredState), m_state(Invalid), m_oldState(Invalid) { ; }
	//! @}

public : //! @name State
	//! @{
	virtual bool isActive() const { return m_state == m_desiredState; }
	//! @}

protected : //! @name Setup
	//! @{
	void setState(State state)
	{
		m_oldState = m_state;

		if (state == Down &&
			(m_oldState == Up || m_oldState == Released))
		{
			m_state = Pressed;
		}
		else if (state == Up &&
			(m_oldState == Down || m_oldState == Pressed))
		{
			m_state = Released;
		}
		else
		{
			m_state = state;
		}
	}
	//! @}

private : //! @name Data
	//! @{
	State m_desiredState;
	State m_state;
	State m_oldState;
	//! @}
};

class MouseButtonCondition
:	public ButtonStateCondition
{
public : //! @name Construction & Destruction
	//! @{
	MouseButtonCondition(OIS::MouseButtonID button, ButtonStateCondition::State state) : ButtonStateCondition(OIS::OISMouse, state), m_button(button) { ; }
	//! @}

protected : //! @name Commands
	//! @{
	virtual void update(const OIS::Object* device)
	{
		dwAssert(device->type() == getDeviceType(), "Invalid device");

		const OIS::Mouse* mouse = static_cast< const OIS::Mouse* >(device);
		const OIS::MouseState& mouseState(mouse->getMouseState());

		setState(mouseState.buttonDown(m_button) ? ButtonStateCondition::Down : ButtonStateCondition::Up);
	}

	virtual InputCondition* clone() const { return new MouseButtonCondition(*this); }
	//! @}

private : //! @name Data
	//! @{
	OIS::MouseButtonID m_button;
	//! @}
};

class KeyStateCondition
:	public ButtonStateCondition
{
public : //! @name Construction & Destruction
	//! @{
	KeyStateCondition(OIS::KeyCode key, ButtonStateCondition::State state) : ButtonStateCondition(OIS::OISKeyboard, state), m_key(key) { ; }
	//! @}

protected : //! @name Commands
	//! @{
	virtual void update(const OIS::Object* device)
	{
		dwAssert(device->type() == getDeviceType(), "Invalid device");

		const OIS::Keyboard* keyboard = static_cast< const OIS::Keyboard* >(device);

		setState(keyboard->isKeyDown(m_key) ? ButtonStateCondition::Down : ButtonStateCondition::Up);
	}

	virtual InputCondition* clone() const { return new KeyStateCondition(*this); }
	//! @}

private : //! @name Data
	//! @{
	OIS::KeyCode m_key;
	//! @}
};

class InputSampler
:	public InputVariableBase
{
public : //! @name Construction & Destruction
	//! @{
	InputSampler(OIS::Type deviceType) : InputVariableBase(deviceType), m_value(0.0f) { ; }
	//! @}

public : //! @name Commands
	//! @{
	virtual InputSampler* clone() const = 0;
	//! @}

public : //! @name State
	//! @{
	float getValue() const { return m_value; }
	//! @}

protected : //! @name Setup
	//! @{
	void setValue(float value) { m_value = value; }
	//! @}

private : //! @name Data
	//! @{
	float m_value;
	//! @}
};

class MouseCursorSampler
:	public InputSampler
{
public : //! @name Types definitions
	//! @{
	enum Mode
	{
		Absolute,
		Relative,
		Default = Relative
	};

	enum Axis
	{
		X,
		Y,
		Z
	};
	//! @}

public : //! @name Construction & Destruction
	//! @{
	MouseCursorSampler(Axis axis, Mode mode = Default) : InputSampler(OIS::OISMouse), m_axis(axis), m_mode(mode) { ; }
	//! @}

protected : //! @name Commands
	//! @{
	virtual void update(const OIS::Object* device)
	{
		dwAssert(device->type() == getDeviceType(), "Invalid device");

		const OIS::Mouse* mouse = static_cast< const OIS::Mouse* >(device);
		const OIS::MouseState& mouseState(mouse->getMouseState());

		const OIS::Axis& mouseAxis((
			m_axis == X ? mouseState.X : (
			m_axis == Y ? mouseState.Y : (
			m_axis == Z ? mouseState.Z : mouseState.X ))));

		setValue((float)( ( m_mode == Absolute ) ? mouseAxis.abs : mouseAxis.rel ));
	}

	virtual InputSampler* clone() const { return new MouseCursorSampler(*this); }
	//! @}

private : //! @name Data
	//! @{
	Axis m_axis;
	Mode m_mode;
	//! @}
};

	} // namespace inputs
} // namespace dw

#endif // !DW_INPUTS_VARIABLE_H
